home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Science⁄Math / VideoToolbox / VideoToolboxSources / SetEntriesQuickly.c < prev    next >
Encoding:
Text File  |  1993-07-09  |  38.7 KB  |  1,016 lines  |  [TEXT/KAHL]

  1. /*
  2. SetEntriesQuickly.c
  3.  
  4. SetEntriesQuickly loads your video card’s clut as quickly as possible.
  5.  
  6. This has been written by many people, and the “final” result has not been tested
  7. on all the computers and video devices it’s meant to support. The best test is
  8. simply to run TimeVideo, which gives it a thorough workout on all of your
  9. computer’s video devices. Please send the report “TimeVideo results” to
  10. denis_pelli@isr.syr.edu, and I’ll add your test results to “Video synch”
  11. (section E). Naturally, we’ll try to fix bugs as they are discovered.
  12.  
  13. Some Macintosh video drivers are poorly written; they take too long (more than a
  14. frame time) to load the clut. This is makes it impossible to do clut animation
  15. for temporal modulation etc., for which one needs to be able to reload the clut
  16. on each frame. At one time many of us thought that the limitation was in
  17. hardware, in the RAMDAC, but we were wrong. Raynald Comptois disassembled
  18. several video drivers and wrote his own programs to load the clut quickly, and
  19. his programs manage to do it within a frame. Raynald was kind enough to share
  20. his code with me. I passed it on to Peter Lennie and Bill Haake, who polished
  21. it, making it compatible with the 68040 processor, and added support for more
  22. cards. I polished their work, made the routines self contained, adding a
  23. “device” argument to allow use in Macs that have more than one video device, and
  24. quickly figuring out all the key parameters (mode, pixelSize, DAC size, and clut
  25. size). There are now two alternative front ends: SetEntriesQuickly() for new
  26. users, and macltset() for backward compatibility with programs that used
  27. Raynald’s original routines. This modularity has increased runtime only
  28. slightly, a fraction of a millisecond.
  29.  
  30. New drivers are hard to write, since they must directly address the registers of
  31. the video card, which are unique to each video card and undocumented. So the
  32. author of a driver must disassemble the original video driver and figure things
  33. out on his or her own. A lot of work.
  34.  
  35. SetEntriesQuickly is unlike the standard video drivers in the following ways:
  36. 1. SetEntriesQuickly always takes less than 2 ms to load the whole clut. 
  37. Some video drivers, e.g. for Apple’s 8•24 card, take several frames (>30 ms) to 
  38. finish loading the clut.
  39. 2A. SetEntriesQuickly does not wait for VBL, so a visible glitch may appear on
  40. the current frame, at least on some older video cards (e.g. Hi-Res, Toby, and
  41. Mac IIci). (You can prevent this glitch by calling SetEntriesQuickly only at
  42. blanking time. Use a VBL task or WaitForBlanking().) Newer cards and computers
  43. seem to have dual ported video clut memory so you can write at any point in the
  44. frame cycle without noticeable glitch, though of course you may want to synch
  45. the update for other reasons.
  46. 2B. A flag argument “waitForNextBlanking” is provided, but at present this
  47. option is only supported for the Toby video card.
  48. 3. SetEntriesQuickly ignores the gamma table, yielding the same result as using
  49. the standard video driver with an uncorrected gamma table. E.g. after calling
  50. GDUncorrectedGamma(device).
  51. 4. SetEntriesQuickly() does nothing and returns an error if the arguments
  52. specify loading of an out-of-range index. 
  53. 5. SetEntriesQuickly() does nothing and returns an error if the “count”
  54. specifies zero entries. This is contrary to the (bizarre) Apple convention that
  55. a setEntries control call with a count corresponding to zero entries will result
  56. in loading of entries specified by the “value” field of the ColorSpec.
  57. 6. SetEntriesQuickly does not have immediate access to the video driver’s
  58. private tables. Therefore the first time you call SetEntriesQuickly() for a 
  59. particular device there is an extra delay of about 1 ms while some key
  60. information is ferreted out. That information is cached, so subsequent calls
  61. for the same device will be fast, spending most of their time loading the clut.
  62.  
  63. Two front ends are provided, for compatibility with two distinct traditions:
  64.  
  65. OSErr SetEntriesQuickly(GDHandle device,short start,short count,ColorSpec *table);
  66.  
  67. SetEntriesQuickly() uses the same calling convention as the VideoToolbox routine
  68. GDSetEntries() and, except for adding the GDHandle argument to specify the
  69. device, is also the same as Apple’s SetEntries() Color Manager routine,
  70. documented in Inside Macintosh V-143. Apple specifies special behavior when
  71. count==-1, but we don’t support that here and simply return with an error. I
  72. suggest that new users use SetEntriesQuickly rather that macltset. “start” is
  73. the index of the first clut entry to load, and should be greater than or equal
  74. to zero. “count” is the number of entries to load, minus 1. (Yes, “minus 1”,
  75. that’s Apple’s convention.) “table” is a Colorspec array. (Each ColorSpec
  76. element is a structure consisting of a two-byte “value”, which is not used, and
  77. a 6-byte “rgb”, which, in turn is a structure of three 16-bit unsigned short
  78. ints: red, green, and blue. Apple’s convention is that the MOST SIGNIFICANT BITS
  79. of the 16-bit color values are used. It’s good practice in your programs to
  80. provide full 16-bit values, so that when you upgrade to fancier video cards with
  81. more-than-eight bit DACs your programs will benefit from the extra precision
  82. without needing any change. Returns zero if successful, nonzero if unsuccessful,
  83. i.e. illegal arguments.
  84.  
  85. short macltset(GDHandle device,short start
  86.     ,unsigned short* red,unsigned short* green,unsigned short* blue,short count1);
  87.  
  88. macltset() uses a calling convention established by Raynald Comtois, and
  89. provides backward compatibility with older programs. “red”, “green”, and “blue”
  90. are arrays of 16-bit unsigned short ints, of which the LEAST SIGNIFICANT BITS
  91. are used. “start” is the index of the first entry to change. “count1” is the
  92. number of entries to change (contrary to Apple’s convention).
  93.  
  94. Both front ends use the same general-purpose subroutine: LoadClut(), which in
  95. turn calls the hardware-specific routine appropriate to the particular video 
  96. device being used.
  97.  
  98. The useMostSignificantBits bit of the “flags” argument specifies whether to use 
  99. Apple’s convention (for users of SetEntriesQuickly) or Raynald’s convention 
  100. (for users of macltset).
  101.  
  102. At the moment all the supported video cards have 8-bit DACs, except the RasterOps
  103. ProColor 32, which has 9-bit DACs. If the useMostSignificantBits flag is true
  104. then you don’t need to worry, as the least significant bit of the 9-bit DAC
  105. simply picks up the next lower bit from your numbers, giving you a tad more
  106. precision. However, if useMostSignificantBits flag is false then, in order to
  107. use the full range of the DAC you must make all your numbers twice as big, or --
  108. cludge time! -- set the useOnly8Bits flag, to request that your 8-bit numbers be
  109. multiplied by two, allowing you to use the whole range of the DAC without
  110. changing the rest of your program, but wasting the DAC’s least significant bit
  111. by setting it permanently to zero.
  112.  
  113. SUSPENDING INTERRUPTS. If you wish, the low-level routines will suspend
  114. interrupts while loading the clut. Presumably Raynald had his reasons for
  115. implementing this, so this “feature” is enabled when you use macltset(). Peter
  116. Lennie writes, “The switch to uninterruptable processing during the write is, I
  117. think, out of the original drivers (though I’m not absolutely sure).  I imagine
  118. it’s to avoid display glitches that would result from some higher priority
  119. interrupt suspending a clut rewrite somewhere in the middle.” However, I (dgp)
  120. don’t see any advantage to suspending interrupts, and believe that there is a
  121. significant downside if you are trying to keep track of the VBL interrupts on
  122. several video cards, since suspending interrupts for 1 or 2 ms might be long
  123. enough to miss a whole VBL interval. Thus SetEntriesQuickly disables this
  124. “feature”. However, this is not a philosophical debate. We all agree that
  125. interrupts should be suspended if doing otherwise would occasionally result in a
  126. visible glitch. Does anyone know?
  127.  
  128. OSErr WaitForNextBlanking(GDHandle device);
  129.  
  130. Waits for beginning of next blanking interval. Currently this supports only the
  131. Toby and HiRes cards (Apple’s original video cards, now obsolete).
  132.  
  133. SPEED. SetEntriesQuickly() is self contained. You simply give it the GDHandle of
  134. your video device (as returned, e.g. by GetScreenDevice), and tell it what you
  135. want to do to the clut. In order to do this for you it needs to figure out a
  136. bunch of stuff about your video device. This research takes time; the first time
  137. you call it for a particular device it takes on the order of 1 ms to look up
  138. stuff. However, it saves this info in a cache, for each device, for quick
  139. retrieval on subsequent occasions. The implication is that programs that use
  140. SetEntriesQuickly ought to call it once just for practice (to get the cache
  141. loading over with) before using it in a situation where speed matters.
  142.  
  143. The coding of the LoadClut “driver” routines is a compromise between the needs
  144. of SetEntriesQuickly and macltset, which both use them. I decided not to write
  145. separate clut loading loops for the two cases (use most- vs. least-significant
  146. bits). I believe (but have not tested) that adding a register offset instead of
  147. using an autoincrement instruction incurs essentially no time penalty because
  148. the processor automatically overlaps the execution of such instructions. So I
  149. think that SetEntriesQuickly is running flat out, and don’t see any prospect of
  150. speeding it up significantly. On the other hand, I suspect that fetching the
  151. least significant byte by doing a byte access to an odd address (for macltset)
  152. does slow things down perhaps 30% (though I haven’t timed it) over doing a word
  153. access to an even address, as Raynald had originally coded it. If that speed
  154. loss is unacceptable, then one could insert an if(flags&useMostSignificantBits)
  155. statement into the relevant subroutine and write two separate loops optimized
  156. for the two cases. My guess is that the current compromise will be acceptable to
  157. all users.
  158.  
  159. IMPROVEMENTS:
  160. It is hoped that others will add to the functionality of this routine. Please
  161. share your enhancements with others by sending them to denis_pelli@isr.syr.edu
  162. for inclusion in the VideoToolbox.
  163.  
  164. Those wishing to support new video devices should begin by buying and reading
  165. Apple’s Designing Cards and Drivers, 3rd Ed., Addison-Wesley, and then use the
  166. VideoToolbox utility GetVideoDrivers to copy all your drivers into resource
  167. files, and use ResEdit with CODE editor to peruse them. The ResEdit CODE editor
  168. is a public domain file distributed by:
  169. Ira L. Ruben
  170. Apple Computer, Inc.
  171. 20525 Mariani Ave., MS: 37-A
  172. Cupertino, Ca. 95014
  173. Ira@Apple.Com
  174. ftp.apple.com:/dts/mac/tools/resedit/resedit-2-1-code-editor...
  175.  
  176. By the way, assembly code is hard to write, read, and maintain,
  177. and the speed advantage is negligible, about 10%. I suggest that
  178. all new code be written in C.
  179.  
  180. It is logical that we identify the video card by the card name,
  181. GDCardName(driver), but in fact getting the card name is very slow (1.5 ms)
  182. whereas getting the driver name is fast, GDName(driver), and would be
  183. sufficiently unique for our purposes. (E.g. the Toby and TFB video cards have
  184. the same driver, and our code works for both cards.) 
  185.  
  186. KNOWN BUGS:
  187. Has not been tested on all the video devices that are supposed to be supported.
  188. Please run the demo TimeVideo, and send the results file to denis_pelli@isr.syr.edu
  189.  
  190. Does not work with the RasterOps ProColor 32. Hopefully this will be fixed soon.
  191.  
  192. The Quadra code requires that start==0. This could probably be figured out and
  193. fixed pretty easily if someone took the time to do so.
  194.  
  195. I recommend using the standard drivers (i.e. GDSetEntries/GDDirectSetEntries)
  196. instead of SetEntriesQuickly for the Toby and High Resolution video cards and
  197. the Mac IIci built-in video. Those standard drivers work fine, whereas for those
  198. devices SetEntriesQuickly produces visible dynamic black specks as it accesses
  199. the clut.
  200.  
  201. None of these routines wait for the vertical blanking interval before loading
  202. the clut. On older video devices--Toby, HiRes, Mac IIci--this results in visible
  203. dynamic black specks on the screen. I (dgp) consider this a bug, but, for most
  204. of these devices I don’t know how to wait for the end of frame, short of setting
  205. up an interrupt. (Just about every video card has a bit that one could monitor,
  206. but its address is usually undocumented.) Newer devices seem to be ok, because of
  207. dual-ported RAMDAC memory. Check this out on your devices by running TimeVideo.
  208.  
  209. HISTORY:
  210. 8/24/92 Original setcardbase and macltset provided by Raynald Comtois
  211. (raco@wjh12.harvard.edu) to Denis Pelli.
  212.  
  213. 10/2/92 Bill Haake added code for the RasterOps ProColor 32, which has 9-bit
  214. DACs and 9-bit entries in the lookup table.
  215.  
  216. 10/1/92 Peter Lennie added code for Quadra internal video.  No provision for
  217. changing the start position in the table, (I couldn't find any relevant
  218. disassembly) so 'start' is ignored, and you should write the whole table.
  219.     
  220. 9/30/92 Bill Haake & Peter Lennie modified the code for the 8x24 card
  221. and the 8x24GC to make it a) work properly in 32-bit mode. b) to fix a bug
  222. (feature?) of the original drivers that prevented the cards running on a Quadra.
  223. The drivers exploit 'byte-smearing' on the 68020 and 68030 (Tech Note 282). This
  224. means that one can move a byte to the lowest byte address of the data register
  225. on the card, when one actually wants to put it at address+3 (!!). The functions
  226. work for all the cards (except toby, which hasn't been tested) and on internal
  227. video in both 24 and 32 bit mode on Quadra 700/950, IIfx or ci running system
  228. 7.0.1.
  229.     
  230. 9/28/92 Peter Lennie added the function findcard.
  231.  
  232. 11/23/92 Denis Pelli (dgp) eliminated all globals because they implicitly
  233. assumed that there is only one video device. All routines now accept a GDHandle
  234. specifying which video device. Simplified the logic of GetCardBase(), minimizing
  235. the dependence on card type.
  236.  
  237. 11/25/92 dgp When USE_MSB is true, all the routines now use the most significant
  238. bits of the 16-bit elements of the user-supplied color tables. When it is false
  239. the least significant bits are used. This is mostly implemented by offseting the
  240. table pointers by one byte and only reading the desired byte. •Generalized
  241. macltset() to work with tables that have an arbitrary element spacing. This
  242. allows it to work with both with Raynald's convention of three arrays of shorts,
  243. and the Apple convention of a ColorSpec array, each element of which consists of
  244. red, green, blue, and value (which is not used). •Added alias "Toby frame buffer
  245. card" for tobycard.
  246.  
  247. 11/27/92 dgp Broke out the code for each card into separate subroutines. This
  248. allows optimal register assignment for each routine, and makes it much easier to read
  249. the THINK C disassembler output. The runtime overhead of loading and unloading
  250. the stack is negligible, and could be eliminated entirely by putting all the
  251. parameters in a structure and passing a pointer to it. •Added a flag,
  252. suspendInterrupts, to make interrupt suspension optional since it may be
  253. undesirable in some applications. (Blocking interrupts for 1 ms could cause you
  254. to miss the interrupt from a video card, especially if you are trying to keep
  255. track of interrupts on several video cards at once.)
  256.  
  257. 11/30/92 dgp Wrote TestCluts, which reads back the clut and checks all
  258. values, and used it to test SetEntriesQuickly() on Quadra 950 internal video,
  259. Mac IIci internal video, hirescard, "Toby frame buffer card", and 8•24 card at
  260. all depths, for both 24- and 32-bit addressing. Toby card was tested on 68020,
  261. 68030, and 68040 processors. •Wrote documentation. •Replaced compile-time constants
  262. USE_MSB and PRO_8BITS by runtime flags passed as arguments. •Added WaitForNextBlanking()
  263. based on code from VideoTFB.c.
  264.  
  265. 12/3/92 dgp Incorporated Peter Lennie's corrections and additions to the comments above.
  266.  
  267. 12/8/92 dgp Added missing "case" to switch in WaitForNextBlanking.
  268.  
  269. 12/13/92 dgp Changed erroneous "&d" to "%d" in a printf. Added some comments to
  270. the documentation above.
  271.             
  272. 12/15/92 dgp Now get mode from device record and leave it in standard form, 
  273. i.e. with the 0x80 bit set, and only strip off that bit when actually necessary,
  274. e.g. in LoadClutMacIIci. 
  275.  
  276. 12/30/92 dgp Make sure routines return zero when there's no error.
  277.  
  278. 2/15/93    dgp Rewrote nonworking LoadClutToby in C, and made it work. Rewrote
  279. nonworking LoadClutx824 in C, and made it work. Fixed sixteenBitMode in
  280. LoadClutQuadra. Use new SwapPriority instead of Get/SetPriority.
  281.  
  282. 2/20/93    dgp    Translated LoadClutGCx824 to C. (It was ignoring the start value.)
  283.  
  284. 3/4/93    dgp    Added macIIsi to list of supported cards, since it uses the same
  285. driver as the Mac IIci. Changed definitions of string types slightly to allow
  286. compilation of this file as a code resource. However, the assembly code
  287. uses more registers than are available to a code resource.
  288.  
  289. 4/13/93    dgp    Removed 68020 requirement by translating an indexed add in LoadMacIIci to
  290. C.
  291.  
  292. 4/17/93 dgp Added support in GetCardBase for old Mac II computers whose ROM's only 
  293. support 24-bit NuBus addressing. 
  294.  
  295. 5/18/93 SetEntriesQuickly now respects the setting of the device's gray/color mode, 
  296. and maps to gray if in the device is in gray mode and pixelSize<=8. Changed prototype
  297. of macltset to specify the red, green, and blue arrays as "unsigned short" instead of 
  298. "short".
  299.  
  300. 7/7/93    dgp    Disabled some global optimizations because THINK C 6 will
  301. crash while compiling if the Radius PowerView is present: "!gopt_induction,!gopt_loop".
  302.  
  303. 7/9/93    dgp check for 32-bit addressing capability.
  304. */
  305. #include "VideoToolbox.h"
  306. #include <assert.h>
  307. #define USE_ONLY_8_BITS_IN_MACLTSET 0    // 1 to use RasterOps ProColor32 as an 8-bit DAC.
  308. #pragma options(assign_registers,honor_register,redundant_loads,defer_adjust)
  309. #pragma options(global_optimizer,!gopt_induction,!gopt_loop,gopt_cse,gopt_coloring)
  310.  
  311. // These are the five user-callable routines:
  312. OSErr WaitForNextBlanking(GDHandle device);
  313. OSErr SetEntriesQuickly(GDHandle device,short start,short count,ColorSpec *table);
  314. short macltset(GDHandle device,short start
  315.     ,unsigned short* red,unsigned short* green,unsigned short* blue,short count1);
  316. short GetCardType(GDHandle device);
  317. char *GetCardBase(GDHandle device);
  318.  
  319. /*
  320. I suggest keeping the following information private to this file. In principle
  321. you could publish these card types and use them in your programs. However, in
  322. practice, I cannot see any point in doing so. If you need to identify the card
  323. name I suggest you simply use the string returned by GDCardName(device) in
  324. GDVideo.c of the VideoToolbox. (Don't forget to call DisposPtr() when you're
  325. through with the string.) Or use GDName(device), which returns the name of the
  326. card's driver, and is much quicker. If you simply want to know whether your
  327. video card is supported by SetEntriesQuickly.c then you can simply make sure
  328. that GetCardType() returns a nonzero cardType.
  329. */
  330. struct vtype {        /* associates card name and id */
  331.     char name[40];
  332.     short id;
  333. };
  334. enum {                /* card identifiers */
  335.     tobycard = 1,
  336.     hirescard,
  337.     macIIci,
  338.     macIIsi,
  339.     x824card,
  340.     x824GCcard,
  341.     quadra700,
  342.     quadra900,
  343.     quadra950,
  344.     procolor32
  345. };
  346. static struct vtype card[] = {    // card name & id        // Original author:
  347.     {"Toby frame buffer card",                tobycard},    // Raynald Comtois
  348.     {"Display_Video_Apple_TFB",                tobycard},    //     "      "
  349.     {"Mac II High-Resolution Video Card",    hirescard},    // Raynald Comtois
  350.     {"Macintosh II Built-In Video",            macIIci},    // Raynald Comtois
  351.     {"Macintosh A Built-In Video",            macIIsi},    //     "      "
  352.     {"Macintosh Display Card",                x824card},    // Raynald Comtois
  353.     {"Macintosh Display Card 8•24 GC",        x824GCcard},// Raynald Comtois
  354.     {"Macintosh E Built-In Video",            quadra700},    // Peter Lennie
  355.     {"Macintosh C Built-In Video",            quadra900},    //     "      "
  356.     {"Macintosh G Built-In Video",            quadra950},    //     "      "
  357.     {"ProColor 32",                            procolor32}    // Bill Haake
  358. };
  359. static char driverName[][40]=        // Not used at present.
  360. {
  361.     "\p.Display_Video_Apple_TFB"    // Apple “Toby frame buffer card”
  362.     ,"\p.Display_Video_Apple_HRVC"    // Apple “Mac II High-Resolution Video Card”
  363.     ,"\p.Display_Video_Apple_MDC"    // Apple 8•24 “Macintosh Display Card”
  364.     ,"\p.Display_Video_Apple_MDCGC"    // Apple 8•24GC
  365.     ,"\p.Display_Video_Apple_RBV1"    // Mac IIci and IIsi built-in video
  366.     ,"\p.Display_Video_Apple_DAFB"    // Quadra 700, 900, 950 built-in video
  367.     ,"\p.RasterOps 1.0 32XL Video Driver"    // Radius ProColor 32
  368. };
  369. enum {                            // Flags passed to LoadClut().
  370.     suspendInterrupts=1,
  371.     useMostSignificantBits=2,
  372.     useOnly8Bits=4,
  373.     waitForNextBlanking=8
  374. };
  375. enum{quadraNonzeroStart=111};    // value returned as error.
  376.     
  377. short LoadClut(GDHandle device,short start,short count
  378.     ,unsigned short* red,unsigned short* green,unsigned short* blue,long elementSpacing,short flags);
  379. OSErr LoadClutProColor(short start,short count,char *r,char *g,char *b
  380.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  381.     ,char *cardBase,short flags);
  382. OSErr LoadClutQuadra(short start,short count,char *r,char *g,char *b
  383.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  384.     ,char *cardBase,short flags);
  385. OSErr LoadClutMacIIci(short start,short count,char *r,char *g,char *b
  386.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  387.     ,char *cardBase,short flags);
  388. OSErr LoadClutHiRes(short start,short count,char *r,char *g,char *b
  389.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  390.     ,char *cardBase,short flags);
  391. OSErr LoadClutx824(short start,short count,char *r,char *g,char *b
  392.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  393.     ,char *cardBase,short flags);
  394. OSErr LoadClutx824GC(short start,short count,char *r,char *g,char *b
  395.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  396.     ,char *cardBase,short flags);
  397. OSErr LoadClutToby(short start,short count,char *r,char *g,char *b
  398.     ,long elementSpacing,short mode,short pixelSize,short clutSize
  399.     ,char *cardBase,short flags);
  400. /******************************************************************************/
  401. /*
  402. The arguments start, count, and table are the same as for the Color Manager call
  403. SetEntries(), documented in Inside Macintosh V-143. (Except that a count==-1 is
  404. considered illegal here.) Apple's ideosyncratic convention is that "count" is
  405. "zero-based", meaning that it is one less than the number of clut entries that
  406. you want to modify. "count" must be at least zero. Returns zero if successful,
  407. nonzero if unsuccessful, i.e. illegal arguments.
  408. */
  409. OSErr SetEntriesQuickly(GDHandle device,short start,short count,ColorSpec *table)
  410. {
  411.     short flags=useMostSignificantBits;
  412.     //flags+=suspendInterrupts;        // Optional, no
  413.     //flags+=waitForNextBlanking;    // Optional, no
  414.  
  415.     return LoadClut(device,start,count
  416.         ,&table[0].rgb.red,&table[0].rgb.green
  417.         ,&table[0].rgb.blue,sizeof(table[0]),flags);
  418. }
  419. /******************************************************************************/
  420. short macltset(GDHandle device,register short start
  421.     ,unsigned short* red,unsigned short* green,unsigned short* blue,short count1)
  422. {
  423.     short flags=0;
  424.     flags+=suspendInterrupts;        // Optional
  425.     #if USE_ONLY_8_BITS_IN_MACLTSET
  426.         flags+=useOnly8Bits;        // Optional
  427.     #endif
  428.     //flags+=waitForNextBlanking;    // Optional
  429.     
  430.     return LoadClut(device,start,count1-1,red,green,blue,sizeof(red[0]),flags);
  431. }
  432. /******************************************************************************/
  433. /*
  434. The first call to GetCardType for a particular device takes 1.5-3 ms, depending
  435. on your computer's speed, because it takes Apple's Slot Manager a long time to
  436. get the card name. However, GetCardType's answers are cached so subsequent calls
  437. for a previously queried device will be fast <100 µs.
  438. */
  439. short GetCardType(GDHandle device)    // returns card type, if known, or zero if not.
  440. {
  441.     register short i;
  442.     short cardType;
  443.     char *name;
  444.     static GDHandle deviceCache[MAX_SCREENS];
  445.     static short typeCache[MAX_SCREENS];
  446.     
  447.     // Do we already know the answer? Check the cache.
  448.     for(i=0;i<MAX_SCREENS;i++)if(device==deviceCache[i])return typeCache[i];
  449.     // Get card name, see if it's in our list of known cards
  450.     name=GDCardName(device);
  451.     cardType=0;
  452.     for (i=0; i<sizeof(card)/sizeof(card[0]); i++){
  453.         if(strcmp(name,card[i].name)==0){
  454.             cardType=card[i].id;
  455.             break;
  456.         }
  457.     }
  458.     DisposePtr(name);
  459.     // Save answer in cache.
  460.     for(i=0;i<MAX_SCREENS;i++)if(deviceCache[i]==0){
  461.         typeCache[i]=cardType;
  462.         deviceCache[i]=device;
  463.         break;
  464.     }
  465.     return cardType;
  466. }
  467. //#error "Yay"
  468. /******************************************************************************/
  469. long internalVideoBase:0xDD8;    // Undocumented System global
  470.  
  471. char *GetCardBase(GDHandle device)
  472. {
  473.     long cardBase,slot;    /* slot must be declared long */
  474.     short cardType;
  475.     
  476.     slot=GetDeviceSlot(device);
  477.     if(slot==0){
  478.         // Built-in video, not in a NuBus slot.
  479.         // E.g.: macIIci,macIIsi,quadra700,quadra900,quadra950
  480.         #if 1
  481.             // This C is equivalent to Raynald's assembly code below.
  482.             cardBase = *(long *)(internalVideoBase + *(long *)internalVideoBase + 56);
  483.         #else
  484.             asm {
  485.                 move.l 0xDD8,a0        /* get card base address */
  486.                 adda.l (a0),a0
  487.                 move.l 56(a0),a1
  488.                 move.l a1,cardBase
  489.             }
  490.         #endif
  491.     }else{
  492.         // Video card in NuBus slot
  493.         cardType=GetCardType(device);
  494.         switch(cardType){
  495.             case x824GCcard:
  496.                 cardBase = slot<<28;    // a superslot
  497.                 break;
  498.             case tobycard:
  499.             case hirescard:
  500.                 cardBase = (slot<<24) | 0xF0000000;
  501.                 cardBase+= (slot<<20);    // Support old Mac II 24-bit NuBus addressing
  502.                 break;
  503.             case procolor32:             // RasterOps
  504.             case x824card:
  505.             default:
  506.                 cardBase = (slot<<24) | 0xF0000000;
  507.                 break;
  508.         }
  509.     }
  510.     return (char *)cardBase;
  511. }
  512. /******************************************************************************/
  513. short LoadClut(GDHandle device,short start,short count
  514.     ,unsigned short* red,unsigned short* green,unsigned short* blue
  515.     ,long elementSpacing,short flags)
  516. {
  517.     char *cardBase;
  518.     short cardType=0,pixelSize,mode;
  519.     short clutSize;     // entries in the lookup table
  520.     OSErr error;
  521.     int i,j;
  522.     short isGray;
  523.     unsigned short grayTable[256];
  524.     
  525.     if(device==NULL)return 1;
  526.     cardType=GetCardType(device);    // takes 1.7 ms the first time for each device.
  527.     if(cardType==0)return 1;
  528.     cardBase=GetCardBase(device);
  529.     clutSize=GDClutSize(device);
  530.     pixelSize=(**(**device).gdPMap).pixelSize;
  531.     mode=(**device).gdMode;
  532.     
  533.     // Check range.
  534.     if(start>clutSize-1 || start<0 || count+start>clutSize-1 || count<0)return 1;
  535.     
  536.     // We're going to use these RAM addresses in 32-bit mode.
  537.     red = (unsigned short *)StripAddress(red);
  538.     green = (unsigned short *)StripAddress(green);
  539.     blue = (unsigned short *)StripAddress(blue);
  540.  
  541.     if(waitForNextBlanking & flags){
  542.         WaitForNextBlanking(device);
  543.     }
  544.     
  545.     isGray=!TestDeviceAttribute(device,gdDevType);
  546.     if(isGray && pixelSize<=8){
  547.         j=elementSpacing/sizeof(*red);
  548.         for(i=0;i<=count;i++){
  549.             grayTable[i]=*red*0.30+*green*0.59+*blue*0.11;
  550.             red+=j;
  551.             green+=j;
  552.             blue+=j;
  553.         }
  554.         elementSpacing=sizeof(grayTable[0]);
  555.         red=green=blue=grayTable;
  556.     }
  557.     
  558.     // After the above setting up, actually loading 256x3 clut entries takes <2 ms.
  559.     switch (cardType) {
  560.         // I packaged the code for each case into a separate subroutine
  561.         // in order to allow the THINK C compiler to optimize each
  562.         // one independently. An important consideration is that the THINK C 5.04
  563.         // compiler disables most optimizations for any function that includes
  564.         // the "asm" directive anywhere within the function. Thus mixing C and assembly
  565.         // will result in inefficient C. No less important, the THINK C Disassemble
  566.         // command is very handy in writing fast C code, but produces an uncommented 
  567.         // listing, which is much easier to read if the separate routines are each 
  568.         // named subroutines.
  569.         case procolor32:
  570.             error=LoadClutProColor(start,count,(char *)red,(char *)green,(char *)blue,
  571.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  572.             break;
  573.         case quadra700:
  574.         case quadra900:
  575.         case quadra950:
  576.             error=LoadClutQuadra(start,count,(char *)red,(char *)green,(char *)blue,
  577.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  578.             break;
  579.         case macIIci:
  580.         case macIIsi:
  581.             error=LoadClutMacIIci(start,count,(char *)red,(char *)green,(char *)blue,
  582.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  583.             break;
  584.         case hirescard:
  585.             error=LoadClutHiRes(start,count,(char *)red,(char *)green,(char *)blue,
  586.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  587.             break;
  588.         case x824card:
  589.             error=LoadClutx824(start,count,(char *)red,(char *)green,(char *)blue,
  590.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  591.             break;
  592.         case x824GCcard:
  593.             error=LoadClutx824GC(start,count,(char *)red,(char *)green,(char *)blue,
  594.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  595.             break;
  596.         case tobycard:
  597.             error=LoadClutToby(start,count,(char *)red,(char *)green,(char *)blue,
  598.                 elementSpacing,mode,pixelSize,clutSize,cardBase,flags);
  599.             break;
  600.     }
  601.     return error;
  602. }
  603. /******************************************************************************/
  604. OSErr LoadClutProColor(short start,register short count
  605.     ,register char *red,register char *green,register char *blue
  606.     ,register long elementSpacing
  607.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  608. {
  609.     char mmuMode=true32b,priority=7;
  610.     register long bitShift;
  611.     Boolean can32;
  612.     long value=0,error;
  613.  
  614.     error=Gestalt(gestaltAddressingModeAttr,&value);
  615.     can32=!error && (value&(1<<gestalt32BitCapable));
  616.     if(useMostSignificantBits & flags){
  617.         bitShift=9;
  618.     }else{
  619.         if(useOnly8Bits & flags) bitShift=1;
  620.         else bitShift=0;
  621.     }
  622.     if(suspendInterrupts & flags)SwapPriority(&priority);
  623.     if(can32)SwapMMUMode(&mmuMode);
  624.     asm {
  625.         move.l cardBase,a1        /* get card base address */
  626.         adda.l    #0xf60000,a1    /* offset to control registers */        
  627.     @9    move.w    start,2(a1)        /* Set the index on the card */
  628.         move.w (red),d1
  629.         add.l elementSpacing,red
  630.         lsl.w bitShift,d1
  631.         move.w d1,14(a1)
  632.         move.w (green),d1
  633.         add.l elementSpacing,green
  634.         lsl.w bitShift,d1
  635.         move.w d1,14(a1)
  636.         move.w (blue),d1
  637.         add.l elementSpacing,blue
  638.         lsl.w bitShift,d1
  639.         move.w d1,14(a1)
  640.         addq.w    #1,start        /* Point to next entry in table */
  641.         dbf count,@9
  642.     }
  643.     if(can32)SwapMMUMode(&mmuMode);
  644.     if(suspendInterrupts & flags)SwapPriority(&priority);
  645.     return 0;
  646. }
  647. OSErr LoadClutQuadra(short start,register short count
  648.     ,register char *red,register char *green,register char *blue
  649.     ,register long elementSpacing
  650.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  651. {
  652.     char mmuMode=true32b,priority=7;
  653.     Boolean can32;
  654.     long value=0,error;
  655.  
  656.     error=Gestalt(gestaltAddressingModeAttr,&value);
  657.     can32=!error && (value&(1<<gestalt32BitCapable));
  658.     if(start!=0){
  659.         //printf("LoadClutQuadra: start must be zero\n");
  660.         return quadraNonzeroStart;
  661.     }
  662.     if(!(useMostSignificantBits & flags)){
  663.         // Point to less significant byte of word.
  664.         red++;
  665.         green++;
  666.         blue++;
  667.     }
  668.     if(suspendInterrupts & flags)SwapPriority(&priority);
  669.     if(can32)SwapMMUMode(&mmuMode);
  670.     if(mode!=sixteenBitMode)asm{
  671.         move.l cardBase,a1
  672.         lea 0x210(a1), a1
  673.         clr.l -16(a1)
  674.     @4    move.b (red),d1
  675.         add.l elementSpacing,red
  676.         move.l d1,(a1)
  677.         move.b (green),d1
  678.         add.l elementSpacing,green
  679.         move.l d1,(a1)
  680.         move.b (blue),d1
  681.         add.l elementSpacing,blue
  682.         move.l d1,(a1)
  683.         dbf count,@4
  684.     }else asm{
  685.     // In sixteenBitMode the clut addressing is weird.
  686.     // I arrived at the following solution by trial and error.
  687.     // It's a kludge, but is still fast enough. dgp.
  688.         move.l cardBase,a1
  689.         lea 0x210(a1), a1
  690.         clr.l -16(a1)
  691.     @44    move.b (red),d1
  692.         move.l d1,(a1)
  693.         move.b (green),d1
  694.         move.l d1,(a1)
  695.         move.b (blue),d1
  696.         move.l d1,(a1)
  697.  
  698.         move.b (red),d1
  699.         move.l d1,(a1)
  700.         move.b (green),d1
  701.         move.l d1,(a1)
  702.         move.b (blue),d1
  703.         move.l d1,(a1)
  704.  
  705.         move.b (red),d1
  706.         move.l d1,(a1)
  707.         move.b (green),d1
  708.         move.l d1,(a1)
  709.         move.b (blue),d1
  710.         move.l d1,(a1)
  711.  
  712.         move.b (red),d1
  713.         move.l d1,(a1)
  714.         move.b (green),d1
  715.         move.l d1,(a1)
  716.         move.b (blue),d1
  717.         move.l d1,(a1)
  718.  
  719.         move.b (red),d1
  720.         move.l d1,(a1)
  721.         move.b (green),d1
  722.         move.l d1,(a1)
  723.         move.b (blue),d1
  724.         move.l d1,(a1)
  725.  
  726.         move.b (red),d1
  727.         move.l d1,(a1)
  728.         move.b (green),d1
  729.         move.l d1,(a1)
  730.         move.b (blue),d1
  731.         move.l d1,(a1)
  732.  
  733.         move.b (red),d1
  734.         move.l d1,(a1)
  735.         move.b (green),d1
  736.         move.l d1,(a1)
  737.         move.b (blue),d1
  738.         move.l d1,(a1)
  739.  
  740.         move.b (red),d1
  741.         add.l elementSpacing,red
  742.         move.l d1,(a1)
  743.         move.b (green),d1
  744.         add.l elementSpacing,green
  745.         move.l d1,(a1)
  746.         move.b (blue),d1
  747.         add.l elementSpacing,blue
  748.         move.l d1,(a1)
  749.  
  750.         dbf count,@44
  751.     }
  752.     if(can32)SwapMMUMode(&mmuMode);
  753.     if(suspendInterrupts & flags)SwapPriority(&priority);
  754.     return 0;
  755. }
  756. OSErr LoadClutMacIIci(register short start,register short count
  757.     ,register char *red,register char *green,register char *blue
  758.     ,register long elementSpacing
  759.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  760. {
  761.     static char realstartindex[] = {0xFE, 0xFC, 0xF0, 0x00,0,0,0};
  762.     char priority=7;
  763.     Boolean can32;
  764.     long value=0,error;
  765.  
  766.     error=Gestalt(gestaltAddressingModeAttr,&value);
  767.     can32=!error && (value&(1<<gestalt32BitCapable));    
  768.     if(!(useMostSignificantBits & flags)){
  769.         // Point to less significant byte of word.
  770.         red++;
  771.         green++;
  772.         blue++;
  773.     }
  774.     if(suspendInterrupts & flags)SwapPriority(&priority);
  775.     mode&=7;
  776.     start+=realstartindex[mode];
  777.     asm {
  778.         move.w mode,d1
  779.         move.l cardBase,a0        // get card base address
  780.         move.l a0,a1
  781. //        move.b #255,8(a0)        // not necessary
  782.         move.b start,(a0)
  783.         addq.l #4,a1
  784.     @3    move.b (red),d1
  785.         add.l elementSpacing,red
  786.         move.b d1,(a1)
  787.         move.b (green),d1
  788.         add.l elementSpacing,green
  789.         move.b d1,(a1)
  790.         move.b (blue),d1
  791.         add.l elementSpacing,blue
  792.         move.b d1,(a1)
  793.         dbf count,@3
  794.     }
  795.     if(suspendInterrupts & flags)SwapPriority(&priority);
  796.     return 0;
  797. }
  798. // High resolution video card
  799. //#define HRVCBase            0x80000
  800. #define HRVCClutAddrReg        0x940E0
  801. #define HRVCClutWDataReg    0x940E4
  802. //#define HRVCClutRDataReg    0x94054
  803. OSErr LoadClutHiRes(short start,register short count
  804.     ,register char *red,register char *green,register char *blue
  805.     ,register long elementSpacing
  806.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  807. {
  808.     char *bytePtr;
  809.     char mmuMode=true32b,priority=7;
  810.     Boolean can32;
  811.     long value=0,error;
  812.  
  813.     error=Gestalt(gestaltAddressingModeAttr,&value);
  814.     can32=!error && (value&(1<<gestalt32BitCapable));    
  815.     if(!(useMostSignificantBits & flags)){
  816.         // Point to less significant byte of word.
  817.         red++;
  818.         green++;
  819.         blue++;
  820.     }
  821.     if(suspendInterrupts & flags)SwapPriority(&priority);
  822.     if(can32)SwapMMUMode(&mmuMode);
  823.     red+=count*elementSpacing;
  824.     green+=count*elementSpacing;
  825.     blue+=count*elementSpacing;
  826.     // We'll start with clut entry start+count, and work
  827.     // down from there to clut entry start. The clut address
  828.     // register counts down automatically.
  829.     *(cardBase+HRVCClutAddrReg)=~(clutSize-1-start-count);
  830.     bytePtr=cardBase+HRVCClutWDataReg;
  831.     // This is the key loop. 
  832.     // This C code is only about 10% slower than the original assembly code.
  833.     elementSpacing= -elementSpacing;
  834.     do{
  835.         *bytePtr=~ *red;
  836.         red+=elementSpacing;
  837.         *bytePtr=~ *green;
  838.         green+=elementSpacing;
  839.         *bytePtr=~ *blue;
  840.         blue+=elementSpacing;
  841.     }while(--count>=0);
  842.     if(can32)SwapMMUMode(&mmuMode);
  843.     if(suspendInterrupts & flags)SwapPriority(&priority);
  844.     return 0;
  845. }
  846. // Macintosh display card (8•24)
  847. //#define MDCVideoBase        0xA00
  848. #define MDCClutAddrReg        0x200200
  849. #define MDCClutDataReg        0x200204
  850. OSErr LoadClutx824(short start,register short count
  851.     ,register char *red,register char *green,register char *blue
  852.     ,register long elementSpacing
  853.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  854. {
  855.     char mmuMode=true32b,priority=7;
  856.     register char *clut;
  857.     char *clutIndex;
  858.     Boolean can32;
  859.     long value=0,error;
  860.  
  861.     error=Gestalt(gestaltAddressingModeAttr,&value);
  862.     can32=!error && (value&(1<<gestalt32BitCapable));
  863.     if(!(useMostSignificantBits & flags)){
  864.         // Point to less significant byte of word.
  865.         red++;
  866.         green++;
  867.         blue++;
  868.     }
  869.     if(suspendInterrupts & flags)SwapPriority(&priority);
  870.     if(can32)SwapMMUMode(&mmuMode);
  871.     clut=cardBase+MDCClutDataReg+3;
  872.     clutIndex=cardBase+MDCClutAddrReg;
  873.     *clutIndex=start;
  874.     for(;count>=0;count--){
  875.         *clut=*red;
  876.         red+=elementSpacing;
  877.         *clut=*green;
  878.         green+=elementSpacing;
  879.         *clut=*blue;
  880.         blue+=elementSpacing;
  881.     }
  882.     if(can32)SwapMMUMode(&mmuMode);
  883.     if(suspendInterrupts & flags)SwapPriority(&priority);
  884.     return 0;
  885. }
  886. // Macintosh display card 8•24 GC
  887. #define MDCgcClutAddrReg    0x6C00000
  888. #define MDCgcClutDataReg    0x6C00004
  889. OSErr LoadClutx824GC(short start,register short count
  890.     ,register char *red,register char *green,register char *blue
  891.     ,register long elementSpacing
  892.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  893. {
  894.     char mmuMode=true32b,priority=7;
  895.     register long *clut;
  896.     char *clutIndex;
  897.     Boolean can32;
  898.     long value=0,error;
  899.  
  900.     error=Gestalt(gestaltAddressingModeAttr,&value);
  901.     can32=!error && (value&(1<<gestalt32BitCapable));
  902.     if(!(useMostSignificantBits & flags)){
  903.         // Point to less significant byte of word.
  904.         red++;
  905.         green++;
  906.         blue++;
  907.     }
  908.     if(suspendInterrupts & flags)SwapPriority(&priority);
  909.     if(can32)SwapMMUMode(&mmuMode);
  910.     clutIndex=cardBase+MDCgcClutAddrReg;
  911.     *clutIndex=start;
  912.     #if 0
  913.         clut=(long *)(cardBase+MDCgcClutDataReg);
  914.         for(;count>=0;count--){
  915.             *clut=(long)(*red)<<24;
  916.             red+=elementSpacing;
  917.             *clut=(long)(*green)<<24;
  918.             green+=elementSpacing;
  919.             *clut=(long)(*blue)<<24;
  920.             blue+=elementSpacing;
  921.         }
  922.     #else
  923.         asm {
  924.             move.l cardBase,a1
  925.             add.l #MDCgcClutDataReg,a1
  926.         @8    move.b (red),d1
  927.             add.l    elementSpacing,red
  928.             ror.l    #8,d1
  929.             move.l d1,(a1)
  930.             move.b (green),d1
  931.             add.l    elementSpacing,green
  932.             ror.l    #8,d1
  933.             move.l d1,(a1)
  934.             move.b (blue),d1
  935.             add.l elementSpacing,blue
  936.             ror.l    #8,d1
  937.             move.l d1,(a1)
  938.             dbf count,@8
  939.         }
  940.     #endif
  941.     if(can32)SwapMMUMode(&mmuMode);
  942.     if(suspendInterrupts & flags)SwapPriority(&priority);
  943.     return 0;
  944. }
  945. // Toby frame buffer
  946. //#define    TFBBase            0x80000
  947. //#define TFBBufMid            0x80008
  948. //#define TFBBufLow            0x8000C
  949. //#define    TFBIBase        0x8fffc
  950. #define TFBClutWDataReg        0x90018
  951. //#define TFBClutRDataReg    0x90028
  952. #define TFBClutAddrReg        0x9001C
  953. #define    TFBReadVSync        0xD0000
  954. //#define    TFBReadVInt        0xD0004
  955. //#define    TFBReadIntlc    0xD0008
  956. //#define    TFBVIntEnable    0xA0000
  957. //#define    TFBVIntDisable    0xA0004
  958. OSErr LoadClutToby(short start,register short count
  959.     ,register char *red,register char *green,register char *blue
  960.     ,register long elementSpacing
  961.     ,short mode,short pixelSize,short clutSize,char *cardBase,short flags)
  962. {
  963.     register long index;
  964.     char mmuMode=true32b,priority=7;
  965.     register char *clut,*clutIndex;
  966.     short shift;
  967.     Boolean can32;
  968.     long value=0,error;
  969.  
  970.     error=Gestalt(gestaltAddressingModeAttr,&value);
  971.     can32=!error && (value&(1<<gestalt32BitCapable));    
  972.     if(!(useMostSignificantBits & flags)){
  973.         // Point to less significant byte of word.
  974.         red++;
  975.         green++;
  976.         blue++;
  977.     }
  978.     index=(count+1)*elementSpacing;
  979.     red+=index;
  980.     green+=index;
  981.     blue+=index;
  982.     shift=8-pixelSize;
  983.     index=start+count+1;
  984.     clut=cardBase+TFBClutWDataReg;
  985.     clutIndex=cardBase+TFBClutAddrReg;
  986.     if(suspendInterrupts & flags)SwapPriority(&priority);
  987.     for(;count>=0;count--,index--){
  988.         *clutIndex=(index<<shift)-1;
  989.         red-=elementSpacing;
  990.         *clut=~*red;
  991.         green-=elementSpacing;
  992.         *clut=~*green;
  993.         blue-=elementSpacing;
  994.         *clut=~*blue;
  995.     }
  996.     if(suspendInterrupts & flags)SwapPriority(&priority);
  997.     return 0;
  998. }
  999.  
  1000. OSErr WaitForNextBlanking(GDHandle device)
  1001. // WaitForNextBlanking waits for the beginning of the next vertical blanking interval.
  1002. // Returns 0 if successful, or 1 if device is not supported.
  1003. {
  1004.     register long *blankingPtr;
  1005.  
  1006.     switch(GetCardType(device)){
  1007.     case tobycard:
  1008.         blankingPtr = (long *) ((char *)GetCardBase(device) + TFBReadVSync);
  1009.         while (*blankingPtr & 1L);    // if we're already blanking, wait till end.
  1010.         while (!(*blankingPtr & 1L)); // wait until beginning of blanking interval.
  1011.         return 0;
  1012.     default:
  1013.         return 1;
  1014.     }
  1015. }
  1016.